跳到主要内容
Cowers://
全部文章
检索与 RAG

BM25 算法原理

BM25 是一种关键词相关性排序算法:它综合考虑关键词是否稀有、在当前文档中出现多少次,以及文档是否过长,为文档计算相关性分数。

修订说明(核实于 2026-08-13,东八区) 本篇复核后未发现事实或计算错误——核心公式、k1 / b 的作用方向、TF 饱和与长度归一化的机制均正确。补充的是几处容易造成误解的边界:

  • 全篇的「词」指分词器产出的 token,不是汉字数或人类认知里的词。中文下这个区别会彻底改变 IDF 和文档长度的取值。
  • 长度修正不是独立的第三个乘数,它嵌在 TF 权重的分母里,与词频耦合。
  • 正文给的 IDF 是 Lucene 系的非负变体,与 BM25 原始论文的形式不同(原始形式在 df > N/2 时为负)。
  • 「文档长度差异大就该调高 b」不成立,要看长文档是内容更丰富还是注水,只能靠评测集决定。
  • 「数据分析」是不是一个查询词,取决于中文分词器配置。

一句话理解

> BM25 是一种关键词相关性排序算法**:它综合考虑关键词是否稀有、在当前文档中出现多少次,以及文档是否过长,为文档计算相关性分数。**

严格地说,BM25 是一公式而不是一个——IDF 项和长度归一化都有多个变体,不同检索引擎的实现和分数并不通用。

1. BM25 解决什么问题

最简单的关键词检索可能直接统计关键词出现次数,但这样会产生问题:

  • 同一个词重复很多次,分数会无限增加。
  • 常见词和稀有词被同等对待。
  • 长文档更容易偶然包含查询词。
  • 只重复一个查询词的文档,可能压过同时命中多个查询词的文档。

例如查询:

Python 数据分析

文档 A:

Python 可以用于数据分析。

文档 B:

Python 是一种编程语言。Python 生态丰富。Python 可以开发网站。

虽然文档 B 中 Python 出现 3 次,但文档 A 同时命中 Python数据分析,通常更相关。

BM25 的目标就是对这种关键词匹配进行更合理的评分。

2. BM25 的三个核心因素

BM25 得分
= 词的稀有程度
× 经过饱和处理的词频(其中已内含文档长度修正)

对应三个核心概念:

  1. IDF:查询词在整个知识库中有多稀有。
  2. TF + k1:查询词在当前文档中出现多少次,并限制重复词的收益。
  3. 文档长度 + b:修正长文档更容易偶然命中关键词的问题。

长度修正不是独立的第三个乘数 上面写成"× 文档长度修正"只是为了先把三个变量拆开讲清楚,实际公式里它不是一个独立相乘的因子。看第 8 节的完整公式就明白了——长度那一项嵌在 TF 权重的分母里:

tf×(k1+1)tf+k1(1b+bDavgdl)\frac{tf \times (k_1+1)}{tf + k_1\cdot\left(1-b+b\cdot\frac{|D|}{\text{avgdl}}\right)}

括号里那一坨才是长度修正。它的作用是改变饱和曲线的陡峭程度:文档越长,分母越大,同样的词频得到的分数越低,且饱和得越快。

为什么这个区别重要: 如果它是独立乘数,"长度惩罚"和"词频收益"就是可以分开调的两件事;而实际上它们耦合在一起——调 b 会同时改变不同词频下的得分曲线形状,不只是整体缩放。这解释了为什么 k1b 一起调时效果不是简单叠加。

⚠️ 全篇的「词」指的是 analyzer 产出的 token,不是自然语言意义上的词 后文所有关于"词频""文档长度"的讨论,统计单位都是分词器(analyzer / tokenizer)切出来的 token,而不是"汉字数"或"人类认知里的词"。

这在中文里差别很大。同一句"数据分析平台":

分词方式 切出来的 token 「数据分析」是一个词吗
字符级(chinese_compatible 数 / 据 / 分 / 析 / 平 / 台 ❌ 是两个不相干的字
jieba 精确模式 数据分析 / 平台 ✅ 是
jieba 搜索引擎模式 数据 / 分析 / 数据分析 / 平台 ✅ 且同时保留了细粒度

这直接决定 IDF 和文档长度的取值:字符级切分下,|D| 是汉字数,而常用字的 df 接近全库,IDF 趋近于 0——检索退化成"谁的常用字多谁排前面"。

所以看到后文"某某词出现了 3 次"时,请理解为"在你所配置的分词器下,该 token 出现了 3 次"。换分词器,全库的 IDF 和平均文档长度都会变,已调好的 k1 / b 也需要重新标定

3. TF:词频

TF 全称为 Term Frequency,表示:

一个词在当前文档中出现了多少次。

例如查询词是 Python

文档 A:Python 是一种编程语言
文档 B:Python 入门,学习 Python 语法和 Python 开发

那么:

TF(A, Python) = 1
TF(B, Python) = 3

仅从词频看,文档 B 应获得更高分。

3.1 词频饱和

BM25 不会让词频线性增长。

0 次 → 1 次:意义很大
1 次 → 2 次:继续加分
2 次 → 3 次:仍然加分,但增量变小
10 次 → 11 次:增加的意义很小

这称为词频饱和

> 一个关键词出现得越多,得分越高;但后续每次重复带来的加分会逐渐减少。

4. IDF:逆文档频率

IDF 全称为 Inverse Document Frequency,表示:

一个词在整个知识库中有多稀有。

规律:

词越常见 → IDF 越低 → 加分越少
词越稀有 → IDF 越高 → 加分越多

例如知识库有 10000 篇文档:

“数据库”出现在 5000 篇文档中
“倒排索引”出现在 200 篇文档中

那么:

IDF(倒排索引) > IDF(数据库)

因为“倒排索引”更稀有,区分能力更强。

4.1 df 的含义

dfDocument Frequency,表示:

有多少篇文档包含这个词。

注意,IDF 看的是“包含该词的文档数量”,不是该词在所有文档中总共出现了多少次。

例如:

Milvus:出现在 5 篇文档中,每篇出现 10 次
数据库:出现在 500 篇文档中,每篇出现 1 次

则:

df(Milvus) = 5
df(数据库) = 500
IDF(Milvus) > IDF(数据库)

其中:

TF:当前文档中出现几次
IDF:整个知识库中有多少篇文档包含它

4.2 常见 IDF 公式

[ IDF(q)=\ln\left(1+\frac{N-df(q)+0.5}{df(q)+0.5}\right) ]

其中:

  • N:知识库中的文档总数。
  • df(q):包含查询词 q 的文档数量。
  • df(q) 越小,IDF 越高。
  • df(q) 越大,IDF 越低。

这是多个 IDF 变体中的一个**,不是唯一的 BM25 公式** 上式是 Lucene / Elasticsearch / Tantivy(因而也是 pg_search 和 Milvus 的 BM25 Function)采用的形式。BM25 原始论文(Robertson 等)里的 IDF 是这样的:

[ IDF_{\text{原始}}(q)=\ln\frac{N-df(q)+0.5}{df(q)+0.5} ]

差别就是外面那个 1+。它不是装饰——原始形式在 df(q) > N/2 时会变成负数,也就是说一个出现在超过半数文档里的词,会给包含它的文档扣分。这在理论上讲得通(这种词是反向信号),但在工程上会带来反直觉的结果:文档因为多包含了一个常见词而排名下降。

加上 1+ 之后,整个表达式恒大于 1,ln 之后恒为正,IDF 最低只会趋近于 0(无贡献),不会变成惩罚。这就是所谓的"非负 IDF 变体"。

实践意义: 换引擎时别假设 BM25 分数可比。同一个查询、同一批文档,Lucene 系和某个按原始论文实现的库会给出不同的分数,甚至不同的排序。这也是 混合检索选型 里强调"融合用 RRF 而不是直接加权分数"的又一个理由——连"BM25 分数"本身都不是一个跨系统统一的量。

5. 文档长度修正

假设两篇文档中 BM25 都只出现 1 次:

文档 A:全文 100 字,主要介绍 BM25
文档 B:全文 5000 字,只顺带提到一次 BM25

只看 TF:

TF(A) = 1
TF(B) = 1

但文档 A 通常更相关,因为它更集中地讨论 BM25。长文档更容易偶然包含关键词,因此 BM25 会进行文档长度修正。

常用符号:

  • dl:当前文档长度。
  • avgdl:知识库中文档的平均长度。
dl / avgdl > 1 → 当前文档偏长
dl / avgdl = 1 → 当前文档长度接近平均值
dl / avgdl < 1 → 当前文档偏短

在关键词词频相同时,明显偏长的文档通常会受到一定惩罚。

> BM25 并不是认为短文档一定更好。长文档中如果关键词出现得足够多,仍然可能获得较高分。

6. 参数 k1

k1 控制:

词频多快进入饱和状态。

常见取值约为:

k1 = 1.2 ~ 2.0

规律:

k1 小 → 词频更快饱和,重复词带来的额外收益较小
k1 大 → 更重视词频,重复词仍能带来较明显的加分

例如,一个系统不希望关键词重复几十次就获得巨大优势,应倾向于使用较小的 k1

7. 参数 b

b 控制:

文档长度对评分的影响有多大。

取值范围通常是:

0 ≤ b ≤ 1

规律:

b = 0 → 完全不考虑文档长度
b 越小 → 越不在意文档长度
b 越大 → 长度修正越明显
b = 1 → 充分考虑文档长度

常见默认值:

b = 0.75

当知识库中文档长度差异很大时,可以先试较大的 b,让长度修正更明显。

⚠️ 但这只是一个尝试方向,不是规律。长度差异大不等于就该调高 b——关键要看长文档是"内容更丰富"还是"注水更多"

  • 长文档确实包含更多有效信息(如完整的技术手册 vs 摘要)→ 惩罚它反而会压掉好结果,b 应当调低
  • 长文档只是因为冗长而偶然命中更多词(如会议记录、聊天日志)→ b 调高才合理。

这两种情况在"长度方差"这个统计量上看起来一模一样,所以光看长度分布决定不了 b。正确做法是在评测集上跑网格搜索(b 取 0 / 0.25 / 0.5 / 0.75 / 1.0),看 nDCG 或 MRR 怎么走。默认值 0.75 是在英文新闻语料上调出来的经验值,换语言、换领域都不保证仍然最优

8. BM25 完整公式

对于查询 Q 和文档 D

[ BM25(D,Q)

\sum_{q\in Q} IDF(q) \times \frac{tf(q,D)(k_1+1)} {tf(q,D)+k_1\left(1-b+b\frac{dl}{avgdl}\right)} ]

符号说明:

符号 含义
Q 用户查询
D 当前文档
q 查询中的某个词
tf(q,D) 查询词 q 在文档 D 中出现的次数
IDF(q) 查询词 q 在知识库中的稀有程度
dl 当前文档长度
avgdl 所有文档的平均长度
k1 控制词频饱和速度
b 控制文档长度修正强度

9. 多个查询词如何评分

对于查询:

Python 数据分析

BM25 会分别计算每个查询词对文档的得分,然后相加:

⚠️ 这里默认了「数据分析」被切成一个** token,而这取决于分词器** 下面的推导把 Python数据分析 当作两个查询词。这个前提由分词器决定,不是理所当然的

  • jieba 精确模式下确实是一个词 → 公式如下所示,两项相加;
  • 字符级分词下,它会变成 /// 四个 token → 公式变成六项相加(Python 可能还会再拆),且每个单字的 IDF 都很低,整个查询的区分度大幅下降;
  • jieba 搜索引擎模式下会同时产出 数据/分析/数据分析 → 项数更多,且存在重复计分(同一处文本被父词和子词各算一次)。

所以查询侧和入库侧必须用同一个分词器——否则查询切出 数据分析、索引里存的却是四个单字,倒排表里根本查不到这个 token,得分直接为 0。这类失败很隐蔽:不报错,只是那个词永远命中不了

[ BM25(D,Q)

score(\text{Python},D) + score(\text{数据分析},D) ]

例如:

文档 Python 得分 数据分析得分 总分
A 1.2 2.5 3.7
B 1.8 0 1.8
C 0 2.8 2.8

最终排序:

A > C > B

文档 B 中 Python 即使出现多次,也会受到词频饱和限制;文档 A 同时命中两个查询词,因此总分更高。

10. 完整直觉示例

查询:

Python 数据分析

文档 A:

Python 可以用于数据分析

文档 B:

Python Python Python 是一种编程语言

文档 A

  • 命中 Python
  • 命中 数据分析
  • 如果 数据分析 较稀有,它的 IDF 较高。
  • 文档较短,长度惩罚较小。
  • 两个查询词的得分会累加。

文档 B

  • 只命中 Python
  • Python 出现 3 次,但词频收益会逐渐饱和。
  • 完全没有命中 数据分析
  • 最终总分通常低于文档 A。

11. 易错点

> 误区 1:词出现得越多,得分就按比例无限增加。 错。BM25 有词频饱和机制。

> 误区 2:IDF 看一个词总共出现了多少次。 错。IDF 主要看有多少篇文档包含这个词。

> 误区 3:长文档一定比短文档差。 错。BM25 只是对长文档进行适当修正,不是直接否定长文档。

> 误区 4:k1 控制文档长度。 错。k1 控制词频饱和,b 控制文档长度修正。

12. 参数速记

k1 小 → 词频更快饱和
k1 大 → 更看重词频
 
b 小 → 不太考虑文档长度
b 大 → 更强地修正文档长度

13. 最终总结

> BM25 对查询中的每个词分别评分,然后累加:

  • IDF 判断词是否稀有;
  • TF 和 k1 判断词在当前文档中出现多少次,并限制重复收益;
  • dl、avgdl 和 b 修正文档长度;
  • 最后将所有查询词的得分相加,得到文档的 BM25 总分。

最简记忆:

BM25 = 稀有词加权 + 词频饱和 + 长度修正

14. 自测

  1. TF 表示什么?
  2. IDF 主要依据“总出现次数”还是“包含该词的文档数量”?
  3. 为什么关键词重复 20 次不会获得 20 倍分数?
  4. k1 较小时,词频饱和更快还是更慢?
  5. b 较大时,对长文档的修正更强还是更弱?
  6. 为什么同时命中多个查询词的文档通常更有优势?

15. 学习边界

以下内容与 BM25 经常一起出现,但不属于 BM25 核心原理,本笔记不展开:

  • 倒排索引的具体实现。
  • 向量检索。
  • 混合检索。
  • RRF 排名融合。
  • Reranker 重排。